/*-------------------------------------------------------------------------
   Copyright (C) 2010 One Laptop per Child

   This program is free software; you can redistribute it and/or modify it
   under the terms of the GNU General Public License as published by the
   Free Software Foundation; either version 2, or (at your option) any
   later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

   In other words, you are welcome to use, share and improve this program.
   You are forbidden to forbid anyone else to use, share and improve
   what you give them.   Help stamp out software-hoarding!

   As a special exception, you may use this file as part of a free software
   library for the XO of the One Laptop per Child project without restriction.
   Specifically, if other files instantiate
   templates or use macros or inline functions from this file, or you compile
   this file and link it with other files to produce an executable, this
   file does not by itself cause the resulting executable to be covered by
   the GNU General Public License.  This exception does not however
   invalidate any other reasons why the executable file might be covered by
   the GNU General Public License.
-------------------------------------------------------------------------*/

#include <cc_compat.h>
#include <stdio.h>
#include "ds2756.h"
#include "onewirebatt.h"
#include "lifebatt.h"
#include "io.h"
#include "power.h"
#include "ac.h"
#include "timer.h"
#include "mppt.h"
#include "sdi.h"
#include "event.h"
#include "battery.h"
#include "lifebatt.h"
#include "ac.h"

#define BATTERY_STATE_DISABLED			0
#define BATTERY_STATE_CHECK_EDGE		1
#define BATTERY_STATE_CHECK_VALID		2
#define BATTERY_STATE_NOT_VALID_EDGE	3
#define BATTERY_STATE_LEDS				4
#define BATTERY_STATE_LEDS_TRICKLE		5
#define BATTERY_STATE_LEDS_CHARGE		6
#define BATTERY_STATE_LEDS_DISCHARGE	7
#define BATTERY_STATE_LEDS_LOW			8
#define BATTERY_STATE_LEDS_CRITICAL		9
#define BATTERY_STATE_LEDS_ERROR		10
#define BATTERY_STATE_LEDS_FULL			11
#define BATTERY_STATE_LEDS_OVERTEMP		12

unsigned char xdata battery_debug_flags;
unsigned char xdata battery_info;
unsigned char xdata battery_state;
unsigned char xdata battery_state_prev;
unsigned char xdata battery_leds_state;
unsigned char xdata battery_leds_state_prev;
unsigned char xdata battery_blink_led_on;

// TODO: Merge this and battery info
unsigned char xdata sMBAT_STATUS;
// TODO: What bits in this are necessary?
unsigned char xdata sMCHARGE_STATUS;
// TODO: What bits of these are used. Consolidate?
unsigned char xdata bBAT_MISCFlag;

int xdata battery_debug_timer;
int xdata battery_timer;
int xdata battery_blink_timer;

static unsigned char xdata battery_runin_enabled;

static void battery_reset(void);

void battery_init_module(void)
{
	battery_state_prev		= 0xff;
	battery_state			= BATTERY_STATE_DISABLED;
//	battery_debug_timer		= 0;
	battery_debug_flags		= BATTERY_DEBUG_SHORT;
//	sMBAT_STATUS 			= 0;
//	sMCHARGE_STATUS 		= 0;
//	battery_leds_state		= 0;
//	battery_leds_state_prev	= 0;
//	battery_blink_timer 	= 0;
//	battery_blink_led_on 	= 0;
//	battery_runin_enabled	= 0;
//	apply_max_power_limit();
}

void battery_generate_events(void)
{
	static unsigned char o_sMBAT_STATUS;
	static unsigned char o_sMCHARGE_STATUS;
	static unsigned char o_battery_info;
	static int bat_event_timer;

	unsigned char diff;
	static char sendevent;


	diff = o_sMBAT_STATUS ^ sMBAT_STATUS;
	if (diff & (bat_in|bat_low|bat_full))
	{
		o_sMBAT_STATUS = sMBAT_STATUS;
		if (!sendevent) sendevent = 1;
	}

	diff = o_battery_info ^ battery_info;
	if (diff & BATTERY_VALID_FLAG)
	{
		o_battery_info = battery_info;
		if (!sendevent) sendevent = 1;
	}

	diff = o_sMCHARGE_STATUS ^ sMCHARGE_STATUS;
	if (diff & (bat_charge|bat_WakeChg))
	{
		o_sMCHARGE_STATUS = sMCHARGE_STATUS;
		if (!sendevent) sendevent = 1;
	}

	switch (sendevent)
	{
		case 1:
			bat_event_timer = get_ms_timer();
			sendevent = 2;
			break;

		case 2:
			if (check_timer(bat_event_timer, 250))
			{
				event_send_event(EVENT_BATTERY_STATUS);
				sendevent = 0;
			}
			break;
	}
}

void battery_start(void)
{
	battery_state = BATTERY_STATE_CHECK_VALID;
	battery_debug_timer = battery_timer = get_ms_timer();
}

void battery_set_debug(unsigned char bits)
{
	battery_debug_flags = bits;
	battery_debug_timer = get_ms_timer();
}

void battery_set_leds_state(unsigned char state)
{
	battery_leds_state= state;
}

void battery_reset_leds(void)
{
	battery_blink_timer = get_ms_timer();
	battery_blink_led_on = 0;
}

void battery_process(void)
{

#if 0
	if (battery_state_prev != battery_state)
	{
		battery_state_prev = battery_state;
		print_tstamp();
		printf("BS: %d\n",(unsigned int) battery_state);
	}
#endif

	battery_generate_events();

	switch (battery_state)
	{
		case BATTERY_STATE_DISABLED:
			break;

		case BATTERY_STATE_CHECK_EDGE:
			// Watch for the falling edge of bat_valid to
			// reset the battery subsystem if the battery is
			// removed or if the battery has some sort of error
			if ( (battery_info & BATTERY_VALID_PREV) &&
					(!(battery_info & BATTERY_VALID_FLAG) ) )
			{
				battery_state = BATTERY_STATE_NOT_VALID_EDGE;
			}
			else
			{
				if ( (!(battery_info & BATTERY_VALID_PREV)) &&
						(battery_info & BATTERY_VALID_FLAG) )
				{
					battery_timer = get_ms_timer();
					print_tstamp();
					puts("Bat Start");
				}
				battery_state = BATTERY_STATE_CHECK_VALID;
			}

			// Update bat_valid_prev with the current state of bat_valid
			if (battery_info & BATTERY_VALID_FLAG)
			{
				battery_info |= BATTERY_VALID_PREV;
			}
			else
			{
				battery_info &= ~BATTERY_VALID_PREV;
			}
			break;

		case BATTERY_STATE_CHECK_VALID:
			// Print out a bunch of battery info if
			// enabled
			if (battery_debug_flags & BATTERY_DEBUG )
			{
				if (check_timer(battery_debug_timer,1000) )
				{
					batt_debug_dump();
					battery_debug_timer = get_ms_timer();
				}
			}

			if (battery_info & BATTERY_VALID_FLAG)
			{
				// It seems the  battery code must be run slower than
				// the onewire or races happen.
				// TODO: track these down and fix them.
				if (check_timer(battery_timer,100))
				{
					battery_timer = get_ms_timer();
					switch (onewire_get_battery_type())
					{
						case 0x22:
							lifepo4_process(BATTERY_BYD);
							break;
					}
				}
			}
			battery_state = BATTERY_STATE_LEDS;
			break;

		case BATTERY_STATE_NOT_VALID_EDGE:
			battery_reset();
			battery_state = BATTERY_STATE_CHECK_VALID;
			break;

		case BATTERY_STATE_LEDS:

			if (battery_leds_state != battery_leds_state_prev)
			{
				battery_leds_state_prev = battery_leds_state;
				battery_reset_leds();
			}
			switch (battery_leds_state)
			{
				case LEDS_STATE_DISABLED:
					battery_state = BATTERY_STATE_CHECK_EDGE;
					break;
				case LEDS_STATE_TRICKLE:
					battery_state = BATTERY_STATE_LEDS_TRICKLE;
					break;
				case LEDS_STATE_CHARGE:
					battery_state = BATTERY_STATE_LEDS_CHARGE;
					break;
				case LEDS_STATE_DISCHARGE:
				case LEDS_STATE_REMOVED:
					battery_state = BATTERY_STATE_LEDS_DISCHARGE;
					break;
				case LEDS_STATE_LOW:
					battery_state = BATTERY_STATE_LEDS_LOW;
					break;
				case LEDS_STATE_CRITICAL:
					battery_state = BATTERY_STATE_LEDS_CRITICAL;
					break;
				case LEDS_STATE_ERROR:
					battery_state = BATTERY_STATE_LEDS_ERROR;
					break;
				case LEDS_STATE_FULL:
					battery_state = BATTERY_STATE_LEDS_FULL;
					break;
				case LEDS_STATE_OVERTEMP:
					battery_state = BATTERY_STATE_LEDS_OVERTEMP;
					break;
				default:
					puts("unknown LED state");
					battery_state = BATTERY_STATE_CHECK_EDGE;
					break;
			}
			break;

		case BATTERY_STATE_LEDS_TRICKLE:
			if (check_timer(battery_blink_timer,500) )
			{
				battery_blink_timer = get_ms_timer();

				if (battery_blink_led_on)
				{
					battery_blink_led_on = 0;
					set_LED_CHG_GREEN_off();
					set_LED_CHG_RED_off();
				}
				else
				{
					battery_blink_led_on = 1;
					set_LED_CHG_GREEN_on();
					set_LED_CHG_RED_on();
				}
			}
			battery_state = BATTERY_STATE_CHECK_EDGE;
			break;
		case BATTERY_STATE_LEDS_CHARGE:
			set_LED_CHG_GREEN_on();
			set_LED_CHG_RED_on();
			battery_state = BATTERY_STATE_CHECK_EDGE;
			break;
		case BATTERY_STATE_LEDS_DISCHARGE:
			set_LED_CHG_GREEN_off();
			set_LED_CHG_RED_off();
			battery_state = BATTERY_STATE_CHECK_EDGE;
			break;
		case BATTERY_STATE_LEDS_LOW:
			set_LED_CHG_GREEN_off();
			set_LED_CHG_RED_on();
			battery_state = BATTERY_STATE_CHECK_EDGE;
			break;
		case BATTERY_STATE_LEDS_CRITICAL:
			if (check_timer(battery_blink_timer,500) )
			{
				battery_blink_timer = get_ms_timer();
				if (battery_blink_led_on)
				{
					battery_blink_led_on = 0;
					set_LED_CHG_GREEN_off();
					set_LED_CHG_RED_on();
				}
				else
				{
					battery_blink_led_on = 1;
					set_LED_CHG_GREEN_on();
					set_LED_CHG_RED_on();
				}
			}
			battery_state = BATTERY_STATE_CHECK_EDGE;
			break;
		case BATTERY_STATE_LEDS_ERROR:
			if (check_timer(battery_blink_timer,500) )
			{
				battery_blink_timer = get_ms_timer();

				if (battery_blink_led_on)
				{
					battery_blink_led_on = 0;
					set_LED_CHG_GREEN_off();
					set_LED_CHG_RED_on();
				}
				else
				{
					battery_blink_led_on = 1;
					set_LED_CHG_GREEN_off();
					set_LED_CHG_RED_off();
				}
			}
			battery_state = BATTERY_STATE_CHECK_EDGE;
			break;

		case BATTERY_STATE_LEDS_FULL:
			set_LED_CHG_GREEN_on();
			set_LED_CHG_RED_off();
			battery_state = BATTERY_STATE_CHECK_EDGE;
			break;
			// Over temp is yellow with a brief pulse of red
		case BATTERY_STATE_LEDS_OVERTEMP:
			if (battery_blink_led_on)
			{
				if (check_timer(battery_blink_timer,800) )
				{
					battery_blink_timer = get_ms_timer();
					battery_blink_led_on = 0;
					set_LED_CHG_GREEN_on();
					set_LED_CHG_RED_on();
				}
			}
			else
			{
				if (check_timer(battery_blink_timer,200) )
				{
					battery_blink_timer = get_ms_timer();
					battery_blink_led_on = 1;
					set_LED_CHG_GREEN_off();
					set_LED_CHG_RED_on();
				}
			}
			battery_state = BATTERY_STATE_CHECK_EDGE;
			break;

		default:
			printf("Unknown battery state: %d\n",(unsigned char)battery_state);
	}
}

void battery_reset(void)
{
	puts("Bat reset");
	Ib_inst_in_mA = 0;
	Vb_inst_in_mV = 0;
	Pb_inst_in_mW = 0;
	SOC = 0;
	lifepo4_reset();
}

void battery_cmd_runin_discharge_enable(void)
{
	print_tstamp();
	puts("Batt runin discharge");

	// Enable short debug view to watch battery
	battery_set_debug(BATTERY_DEBUG|BATTERY_DEBUG_SHORT);

	// force unplugged
	ac_monitor_enable(0);
	ac_config_unplugged();

	// shut off mppt
	mppt_cmd_enable(0);
	mppt_cmd_set_limit(mppt_get_minpower_limit());

	// no charging
	life_charge_off();

	battery_clear_full_flag();

	battery_runin_enabled = 1;

	// 1.5 did this but it should not be necessary on 1.75
//	set_LED_CHG_GREEN_off();
//	set_LED_CHG_RED_off();
}

void battery_cmd_runin_discharge_disable(void)
{
	print_tstamp();
	puts("Batt runin discharge stop");
	mppt_cmd_enable(MPPT_ENABLE_DEFAULT);
	mppt_cmd_set_limit(mppt_get_maxpower_limit());
	ac_monitor_enable(1);
	battery_runin_enabled = 0;

	// 1.5 did this but it should not be necessary on 1.75
//	battery_reset();
}

unsigned char battery_is_in_runin(void)
{
	return battery_runin_enabled;
}

unsigned char battery_is_charging(void)
{
	return (sMCHARGE_STATUS & bat_charge);
}

unsigned char battery_is_full(void)
{
	return (sMBAT_STATUS & bat_full);
}

#ifdef UNUSED
unsigned char battery_is_low(void)
{
	return (sMBAT_STATUS & bat_low);
}

unsigned char battery_is_error(void)
{
	return (sMBAT_STATUS & bat_error);
}

unsigned char battery_is_critical(void)
{
	return (sMBAT_STATUS & bat_critical);
}
#endif

unsigned char battery_is_good(void)
{
	return (battery_info & BATTERY_GOOD_FLAG);
}

void battery_clear_full_flag(void)
{
	GAUGE_CACHE[DS_BAT_MISC_FLAG] &= ~FULLY_CHARGED;
	bBAT_MISCFlag = GAUGE_CACHE[DS_BAT_MISC_FLAG];
	sMBAT_STATUS &= ~bat_full;
	bHDQ_ACCESS_FLAG |= BATT_EEPROM0_UPDATE;
}
